import types, redcell_wrapper as rw


def importKey(k):
    if isinstance(k, (int, long)):
        return k
    else:
        raise ValueError(k)


def importValue(v):
    if isinstance(v, (int, long, float, str, dict, types.NoneType)):
        return v
    elif isinstance(v, bool):
        return 1 if v else 0
    elif isinstance(v, buffer):
        return str(v)
    elif isinstance(v, unicode):
        return v.encode('UTF-8')
    else:
        raise ValueError(v)
        

def merge(dst, src):
    """Merge the dict 'src' onto the dict 'dst'. That means all 
    key-value pairs that src has will be added to dst, and the 
    old values in src with the same keys (if any) will be overwritten. 
    Except that, if both the old value and the new value are dicts, 
    the new dict will be merged onto the old one, using this function 
    as well, rather than to overwrite."""

    for k, v in src.iteritems():
        if isinstance(v, dict):
            try:
                if not isinstance(dst[k]):
                    dst[k] = {}
            except KeyError:
                dst[k] = {}
            merge(dst[k], v)
        else:
            dst[k] = v



INDENT = '   '

def dump(tab, lv = 0):
    """Dump a dict as a redcell table to a string of human-readable 
    format, mainly for debugging reasons. all nested dicts will be 
    dumped recursively. The whole dict-tree must not contain keys
    or values of incompatible types with redcell."""

    prefix = INDENT * lv
    innerprefix = prefix + INDENT
    res = '{\n'
    expkey = 0
    for k, v in sorted(tab.iteritems()):
        ik = importKey(k)
        iv = importValue(v)

        res += innerprefix + str(ik) + ('  ' if ik == expkey else '. ')
        if isinstance(iv, types.NoneType):
            res += 'NIL'
        elif isinstance(iv, (int, long)):
            res += 'INT ' + str(iv)
        elif isinstance(iv, float):
            res += 'REAL ' + str(iv)
        elif isinstance(iv, str):
            res += 'STR ' + iv
        elif isinstance(iv, dict):
            res += dump(iv, lv + 1)
        res += '\n'
        expkey = ik + 1
    return res + prefix + '}'

    
def writeItem(buf, item):
    ii = importValue(item)

    if isinstance(ii, types.NoneType):
        buf.putNil()
    elif isinstance(ii, (int, long)):
        buf.putInteger(ii)
    elif isinstance(ii, float):
        buf.putReal(ii)
    elif isinstance(ii, str):
        buf.putString(ii)
    elif isinstance(ii, dict):
        if ii:
            buf.putSub()
            writeTable(buf, ii)
        else:
            buf.putEmptySub()
    

def writeTable(buf, tab):
    expkey = 0
    for k, v in sorted(tab.iteritems()):
        ik = importKey(k)
        iv = importValue(v)

        keyinc = ik - expkey
        if keyinc > 0:
            buf.putKeySkip(keyinc)
        writeItem(buf, iv)
        expkey = ik + 1
    buf.putEnd()


def pack(tab):
    """Serialize a dict and all sub-dicts it contains recursively 
    into a string of the redcell binary format. The whole dict-tree 
    must not contain keys or values of incompatible types with redcell."""
    buf = rw.StringWriter()
    writeTable(buf, tab)
    return buf.str()


class MarshalError(Exception):
    pass


def readItem(buf, tab, flag, key):
    print flag
    if flag == rw.F_NIL:
        tab[key] = None
    elif flag == rw.F_FALSE:
        tab[key] = rw.FALSE
    elif flag == rw.F_TRUE:
        tab[key] = rw.TRUE
    elif flag == rw.F_POS:
        tab[key] = buf.getInteger(0)
    elif flag == rw.F_NEG:
        tab[key] = buf.getInteger(-1)
    elif flag == rw.F_FLOAT:
        tab[key] = buf.getFloat()
    elif flag == rw.F_DOUBLE:
        tab[key] = buf.getDouble()
    elif flag == rw.F_STR:
        tab[key] = buf.getString()
    elif flag == rw.F_EMPTYSUB:
        tab[key] = {}
    elif flag == rw.F_SUB:
        sub = {}
        readTable(buf, sub)
        tab[key] = sub
    else:
        raise MarshalError('Bad stream content')


def readTable(buf, tab):
    key = 0
    while True:
        flag = buf.getFlag()
        if flag == rw.F_KEYSKIP:
            key += buf.getInteger(0)
        elif flag == rw.F_END:
            break
        else:
            readItem(buf, tab, flag, key)
            key += 1


def unpack(raw):
    """Deserialize a string of the redcell binary format into a nested tree 
    of dicts. If the the format is wrong, a ValueError will be raised."""
    buf = rw.StringReader(raw)
    tab = {}
    try:
        readTable(buf, tab)
    except rw.MarshalError:
        raise MarshalError('Unexpected end of stream')
    return tab


############################################

def test():
    t = {2: 123, 0: 'hello', 1: { 4: 3.141, 5: None}}
    
    print dump(t)

    def strbin(s):
        return ' '.join(['%02x' % ord(c) for c in s])

    raw = pack(t)
    print strbin(raw)
    
    s = unpack(raw)
    print dump(s)
    print s


if __name__ == '__main__':
    test()


